﻿using Microsoft.Kinect;
using Microsoft.Kinect.Toolkit;
using System;
using System.Drawing;
using System.Windows;
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using AForge.Video;
using AForge.Video.DirectShow;
using System.Diagnostics; // VideoCaptureDevice


namespace FaceTrackerAMEE
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public partial class MainWindow : Window
    {
        #region Fields

        /// <summary>Kinect finder</summary>
        readonly KinectSensorChooser _sensorChooser = new KinectSensorChooser();
        /// <summary>Format definitions, coming from kinect device.</summary>
        ColorImageFormat _currentColorImageFormat = ColorImageFormat.Undefined;
        /// <summary>The raw color image</summary>
        private byte[] _colorImageData;
        /// <summary>The drawable color Image, for display.</summary>
        private WriteableBitmap _colorImageWritableBitmap;
        /// <summary>... for CopyPixelDataTo()</summary>
        static readonly int Bgr32BytesPerPixel = (PixelFormats.Bgr32.BitsPerPixel + 7) / 8;

        //Anlegen eines Webcam-Objektes
        VideoCaptureDevice videoSource;


        #endregion


        /// <summary>
        /// Initializes a new instance of the <see cref="MainWindow"/> class.
        /// </summary>
        public MainWindow() {
            this.Initialized += MainWindow_Initialized;
            InitializeComponent();
            this.Closing += MainWindow_Closing;
        }

        

        /// <summary>
        /// Handles the Initialized event of the MainWindow control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="EventArgs"/> instance containing the event data.</param>
        /// <remarks>
        /// Binding the FaceTracker's KinectProperty with the SensorChooser,
        /// add KinectChanged handler and start SensorChooser
        /// </remarks>
        void MainWindow_Initialized(object sender, EventArgs e) {
            // Kinect einbinden
            var faceTrackingViewerBinding = new Binding("Kinect") { Source = _sensorChooser };
            faceTrackingViewer.SetBinding(FaceTrackerView.KinectProperty, faceTrackingViewerBinding);
            _sensorChooser.KinectChanged += SensorChooserOnKinectChanged;
            _sensorChooser.Start();

            // WebCam einbinden

            // Anlegen einer Liste mit allen Videoquellen. (Hier können neben der gewünschten Webcam
            // auch TV-Karten, etc. auftauchen)
            FilterInfoCollection videosources = new FilterInfoCollection(FilterCategory.VideoInputDevice);

            // Überprüfen, ob mindestens eine Aufnahmequelle vorhanden ist
            if (videosources != null) {
                // Die erste Aufnahmequelle an unser Webcam Objekt binden
                // (habt ihr mehrere Quellen, muss nicht immer die erste Quelle die
                // gewünschte Webcam sein!)
                videoSource = new VideoCaptureDevice(videosources[0].MonikerString);

                try {
                    //Überprüfen ob die Aufnahmequelle eine Liste mit möglichen Aufnahme-
                    //Auflösungen mitliefert.
                    if (videoSource.VideoCapabilities.Length > 0) {
                        string highestSolution = "0;0";
                        //Das Profil mit der höchsten Auflösung suchen
                        for (int i = 0; i < videoSource.VideoCapabilities.Length; i++) {
                            if (videoSource.VideoCapabilities[i].FrameSize.Width > Convert.ToInt32(highestSolution.Split(';')[0]))
                                highestSolution = videoSource.VideoCapabilities[i].FrameSize.Width.ToString() + ";" + i.ToString();
                        }
                        //Dem Webcam Objekt ermittelte Auflösung übergeben
                        videoSource.VideoResolution = videoSource.VideoCapabilities[Convert.ToInt32(highestSolution.Split(';')[1])];
                    }
                }
                catch (Exception ){
                    Debug.WriteLine("Fehler bei Initialisierung der WebCam Auflösung", "Warning MainWindow");
                }

                //NewFrame Eventhandler zuweisen anlegen.
                //(Dieser registriert jeden neuen Frame der Webcam)
                videoSource.NewFrame += new AForge.Video.NewFrameEventHandler(videoSource_NewFrame);

                //Das Aufnahmegerät aktivieren
                videoSource.Start();
            }

        }

        /// <summary>
        /// Handles the NewFrame event of the videoSource control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="eventArgs">The <see cref="AForge.Video.NewFrameEventArgs"/> instance containing the event data.</param>
        void videoSource_NewFrame(object sender, AForge.Video.NewFrameEventArgs eventArgs) {
            // Jedes ankommende Objekt als Bitmap casten und der Picturebox zuweisen
            // (Denkt an das ".Clone()", um Zugriffsverletzungen aus dem Weg zu gehen.)

            //pictureBoxVideo.BackgroundImage = (Bitmap)eventArgs.Frame.Clone();
        }

        /// <summary>
        /// Handles the Closing event of the MainWindow control.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.ComponentModel.CancelEventArgs"/> instance containing the event data.</param>
        void MainWindow_Closing(object sender, System.ComponentModel.CancelEventArgs e) {
            //Beim Beenden des Programmes die Webcam wieder "freigeben",
            //damit sie von anderen Anwendungen benutzt werden kann.
            if (videoSource != null && videoSource.IsRunning) {
                videoSource.SignalToStop();
                videoSource = null;
            }
        }


        /// <summary>
        /// Handles the KinectChange event from KinectSensorChooser.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="kinectChangedEventArgs">The <see cref="KinectChangedEventArgs"/> instance containing the event data.</param>
        /// <remarks>
        /// Disable old Sensor, enable new Sensor streams and register AllFranesReady handler.
        /// </remarks>
        void SensorChooserOnKinectChanged(object sender, KinectChangedEventArgs kinectChangedEventArgs) {
            var oldSensor = kinectChangedEventArgs.OldSensor;
            var newSensor = kinectChangedEventArgs.NewSensor;

            if (oldSensor != null) {
                oldSensor.AllFramesReady -= onAllFramesReady;
                oldSensor.ColorStream.Disable();
                oldSensor.DepthStream.Disable();
                oldSensor.DepthStream.Range = DepthRange.Default;
                oldSensor.SkeletonStream.Disable();
                oldSensor.SkeletonStream.EnableTrackingInNearRange = false;
                oldSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Default;
            }

            if (newSensor != null) {
                try {
                    newSensor.ColorStream.Enable(ColorImageFormat.RgbResolution640x480Fps30);
                    newSensor.DepthStream.Enable(DepthImageFormat.Resolution320x240Fps30);
                    try {
                        // This will throw on non Kinect For Windows devices.
                        newSensor.DepthStream.Range = DepthRange.Near;
                        newSensor.SkeletonStream.EnableTrackingInNearRange = true;
                    }
                    catch (InvalidOperationException) {
                        newSensor.DepthStream.Range = DepthRange.Default;
                        newSensor.SkeletonStream.EnableTrackingInNearRange = false;
                    }

                    newSensor.SkeletonStream.TrackingMode = SkeletonTrackingMode.Seated;
                    newSensor.SkeletonStream.Enable();
                    newSensor.AllFramesReady += onAllFramesReady;
                }
                catch (InvalidOperationException) {
                    // This exception can be thrown when we are trying to
                    // enable streams on a device that has gone away.  This
                    // can occur, say, in app shutdown scenarios when the sensor
                    // goes away between the time it changed status and the
                    // time we get the sensor changed notification.
                    //
                    // Behavior here is to just eat the exception and assume
                    // another notification will come along if a sensor
                    // comes back.
                }
            }
        }

        /// <summary>
        /// Handle All Frames Ready Event from Kinect sensor.<para>
        /// Copies the color frame to the Bitmap for display
        /// </para>
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="allFramesReadyEventArgs">The <see cref="AllFramesReadyEventArgs"/> instance containing the event data.</param>
        void onAllFramesReady(object sender, AllFramesReadyEventArgs allFramesReadyEventArgs) {
            // read and dispose at least the color frame, then...
            using (var colorImageFrame = allFramesReadyEventArgs.OpenColorImageFrame()) {
                if (colorImageFrame == null) {
                    return;
                }

                // if Format changed (or new), init Images.
                var haveNewFormat = _currentColorImageFormat != colorImageFrame.Format;
                if (haveNewFormat) {
                    initNewImage(colorImageFrame);
                }

                // ...make a copy of the color frame for displaying:
                colorImageFrame.CopyPixelDataTo(_colorImageData);
                _colorImageWritableBitmap.WritePixels(
                    new Int32Rect(0, 0, colorImageFrame.Width, colorImageFrame.Height), // Source Rect
                    _colorImageData,                                                    // Pixels
                    colorImageFrame.Width * Bgr32BytesPerPixel,                         // Stride
                    0);                                                                 // Offset
            }
        }

        /// <summary>
        /// Initializes the Raw Image and the displayed Bitmap.
        /// </summary>
        /// <param name="colorImageFrame">The color image frame.</param>
        private void initNewImage(ColorImageFrame colorImageFrame) {
            _currentColorImageFormat = colorImageFrame.Format;
            _colorImageData = new byte[colorImageFrame.PixelDataLength];
            _colorImageWritableBitmap = new WriteableBitmap(
                colorImageFrame.Width, colorImageFrame.Height, 96, 96, PixelFormats.Bgr32, null);
            ColorImage.Source = _colorImageWritableBitmap;
        }
    }
}
